﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WcfContrib.Client.Behaviors;
using System.ServiceModel;
using System.Threading;
using WcfContrib.Resources;
using WcfContrib.Helpers;

namespace WcfContrib.Client
{
    class DefaultChannelProvider : IChannelProvider, IDisposable
    {
        ClientChannel _clientChannel;
        object _sharedChannel;
        ReaderWriterLockSlim _channelSync;

        public DefaultChannelProvider(ClientChannel clientChannel)
        {
            _clientChannel = clientChannel;
            _channelSync = new ReaderWriterLockSlim();
        }

        public ICommunicationObject GetChannel(ChannelFactory factory, ChannelContextMode mode, out bool isCreated)
        {
            if (mode == ChannelContextMode.Shared)
            {
                if (!_channelSync.TryEnterUpgradeableReadLock(ClientChannel.ChannelWriterLockTimeoutInMs))
                {
                    throw new InvalidOperationException(ExceptionMessages.ClientChannel_LockTimeout);
                }

                object channel = _sharedChannel;
                bool isCreatedInternal = false;

                try
                {
                    //make sure the reusable channel is initialized
                    RunSyncReusableChannelAction(true, () => _sharedChannel == null, true,
                    () =>
                    {
                        _sharedChannel = _clientChannel.CreateChannelCoreInternal(factory);
                        channel = _sharedChannel;
                        isCreatedInternal = true;
                    });

                    isCreated = isCreatedInternal;

                    return channel as ICommunicationObject;
                }
                finally
                {
                    _channelSync.ExitUpgradeableReadLock();
                }
            }
            else
            {
                isCreated = true;

                return _clientChannel.CreateChannelCoreInternal(factory) as ICommunicationObject;
            }
        }

        public void AbortChannel(object channel, ChannelContextMode mode)
        {
            if (mode == ChannelContextMode.Shared)
            {
                RunSyncReusableChannelAction(true, () => _sharedChannel != null, true, 
                    () => 
                    {
                        AbortInternal(_sharedChannel);

                        _sharedChannel = null;
                    });
            }
            else
            {
                AbortInternal(channel);
            }
        }

        void AbortInternal(object channel)
        {
            ChannelHelper.AbortChannel(channel as ICommunicationObject);
        }

        public void CloseChannel(object channel, ChannelContextMode mode)
        {
            if (mode == ChannelContextMode.Shared)
            {
                RunSyncReusableChannelAction(true, () => _sharedChannel != null, true,
                    () =>
                    {
                        CloseInternal(_sharedChannel);

                        _sharedChannel = null;
                    });
            }
            else
            {
                CloseInternal(channel);
            }
        }

        void CloseInternal(object channel)
        {
            ChannelHelper.CloseChannel(channel as ICommunicationObject);
        }

        public void CloseChannelAsync(object channel, ChannelContextMode mode, int timeoutInSeconds)
        {
            if (mode == ChannelContextMode.Shared)
            {
                RunSyncReusableChannelAction(true, () => _sharedChannel != null, true,
                    () =>
                    {
                        CloseAsyncInternal(_sharedChannel, timeoutInSeconds);

                        _sharedChannel = null;
                    });
            }
            else
            {
                CloseAsyncInternal(channel, timeoutInSeconds);
            }
        }

        void CloseAsyncInternal(object channel, int timeoutInSeconds)
        {
            ChannelHelper.CloseAsync(timeoutInSeconds, channel as ICommunicationObject);
        }

        public void TerminateChannel(object channel, ChannelContextMode mode)
        {
            if (mode == ChannelContextMode.Shared)
            {
                TerminateChannelInternal(_sharedChannel, () => _sharedChannel = null);
            }
            else
            {
                TerminateChannelInternal(channel, null);
            }
        }

        void TerminateChannelInternal(object channel, Action syncAction)
        {
            if (channel != null)
            {
                //we use upgradable lock with no degrade to reader lock because nothing is done unless write is needed
                if (!_channelSync.TryEnterUpgradeableReadLock(ClientChannel.ChannelWriterLockTimeoutInMs))
                {
                    throw new InvalidOperationException(ExceptionMessages.ClientChannel_LockTimeout);
                }

                try
                {
                    RunSyncReusableChannelAction(true, () => channel != null, true,
                        () =>
                        {
                            ICommunicationObject com = (ICommunicationObject)channel;

                            if (com.State == CommunicationState.Faulted || ClientChannel.DisposeChannelUsingAbort)
                            {
                                AbortInternal(channel);
                            }
                            else if (com.State == CommunicationState.Opened || com.State == CommunicationState.Opening)
                            {
                                CloseInternal(channel);
                            }

                            if (syncAction != null)
                            {
                                syncAction();
                            }
                        });
                }
                finally
                {
                    _channelSync.ExitUpgradeableReadLock();
                }
            }
        }

        void RunSyncReusableChannelAction(bool isWriteLock, Func<bool> shouldContinue, bool checkTwice, Action action)
        {
            if (shouldContinue())
            {
                bool release = true;

                if (isWriteLock)
                {
                    if (_channelSync.IsWriteLockHeld)
                    {
                        release = false;
                    }
                    else if (!_channelSync.TryEnterWriteLock(ClientChannel.ChannelWriterLockTimeoutInMs))
                    {
                        throw new InvalidOperationException(ExceptionMessages.ClientChannel_LockTimeout);
                    }
                }
                else
                {
                    if (_channelSync.IsReadLockHeld)
                    {
                        release = false;
                    }
                    else if (!_channelSync.TryEnterReadLock(ClientChannel.ChannelWriterLockTimeoutInMs))
                    {
                        throw new InvalidOperationException(ExceptionMessages.ClientChannel_LockTimeout);
                    }
                }

                try
                {
                    if (!checkTwice || shouldContinue())
                    {
                        action();
                    }
                }
                finally
                {
                    if (release)
                    {
                        if (isWriteLock)
                        {
                            _channelSync.ExitWriteLock();
                        }
                        else
                        {
                            _channelSync.ExitReadLock();
                        }
                    }
                }
            }
        }

        public void Dispose()
        {
            TerminateChannel(_sharedChannel, ChannelContextMode.Shared);

            _channelSync.Dispose();
        }
    }
}
